#include "Timeout.h"
#include "Assert.h"

#ifdef __cplusplus
extern "C" {
#endif

#include <signal.h>
#include <errno.h>

#ifdef __cplusplus
}
#endif

#include <cppunit/Message.h>

#include <iostream>
#include <string>
#include <cstring>
#include <sstream>

using namespace std;

Timeout Timeout::timeout;

Timeout::Timeout():
	timeInMiliseconds(0),
	timerID(0) {

}

// Reliable version of signal(), using POSIX sigaction().
Timeout::SignalHandler * Timeout::setSignalHandler(int signalID, SignalHandler * handler) throw (CppUnit::Exception) {
	struct sigaction signalAction;
	struct sigaction prevSignalAction;

	signalAction.sa_handler = handler;
	sigemptyset(&signalAction.sa_mask);
	signalAction.sa_flags = 0;
	if (signalID == SIGALRM) {
#ifdef SA_INTERRUPT
		signalAction.sa_flags |= SA_INTERRUPT;
#endif
	} else {
#ifdef  SA_RESTART
		signalAction.sa_flags |= SA_RESTART;
#endif
	}
	if (sigaction(signalID, &signalAction, &prevSignalAction) != 0) {
		ostringstream errorMessage;
		errorMessage << "Cannot link handler to signal: " << strerror(errno);
		throw CppUnit::Exception(CppUnit::Message(errorMessage.str()),
				CPPUNIT_SOURCELINE());
	}
	return prevSignalAction.sa_handler;
}


void Timeout::handler(int alarmSignalID) throw (CppUnit::Exception) {
	throw CppUnit::Exception(CppUnit::Message(string("Timeout elapsed.")), Timeout::timeout.timeoutStartLine);
}

void Timeout::start(int timeInMiliseconds, CppUnit::SourceLine timeoutStartLine) throw (CppUnit::Exception) {
	ASSERT(timeInMiliseconds > 0);

	Timeout::timeout.timeInMiliseconds = timeInMiliseconds;
	Timeout::timeout.timeoutStartLine = timeoutStartLine;

	struct sigevent alarmEvent;
	memset(& alarmEvent, 0, sizeof(alarmEvent));
	alarmEvent.sigev_signo = SIGALRM;
	Timeout::setSignalHandler(SIGALRM, Timeout::handler);

	if (timer_create(CLOCK_REALTIME, & alarmEvent, & Timeout::timeout.timerID) != 0) {
		ostringstream errorMessage;
		errorMessage << "Cannot create timer: " << strerror(errno);
		throw CppUnit::Exception(CppUnit::Message(errorMessage.str()),
				CPPUNIT_SOURCELINE());
	}
	struct itimerspec timeoutInterval;
	memset(& timeoutInterval, 0, sizeof(timeoutInterval));
	timeoutInterval.it_value.tv_sec = timeInMiliseconds / 1000;
	timeoutInterval.it_value.tv_nsec = (timeInMiliseconds % 1000) * 1000000;
	if(timer_settime(Timeout::timeout.timerID, 0, & timeoutInterval, NULL) != 0) {
		ostringstream errorMessage;
		errorMessage << "Cannot set time: " << strerror(errno);
		throw CppUnit::Exception(CppUnit::Message(errorMessage.str()),
				CPPUNIT_SOURCELINE());
	}
}

void Timeout::stop() throw (CppUnit::Exception) {
	if(timer_delete(Timeout::timeout.timerID) != 0) {
		ostringstream errorMessage;
		errorMessage << "Cannot delete timer: " << strerror(errno);
		throw CppUnit::Exception(CppUnit::Message(errorMessage.str()),
				CPPUNIT_SOURCELINE());
	}
}
